﻿using ProtoBuf.Meta;
using SyncCore.Sync.Helpers;
using SyncCore.Sync.Models;
using SyncDemo.Sync.Models;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SyncCore.Sync
{
    public partial class Peer : IDisposable
    {
        #region consts

        public const int BufferSize
#if DEBUG
= 128;
#else
= 2 * 1024;
#endif

        public const string ReceiveSocketCreateMutexName = "RECEIVESOCKETCREATEMUTEXNAME23460264";

        #endregion

        #region props
        public AddressPortStateList AddressPortInfoList { get; set; }
        #endregion

        #region fields

        private int _port;

        private Socket _receiveSocket; // 接收。
        private Socket _boardcastSocket; // 广播。

        private Mutex _receiveSocketCreateMutex; // 创建 ReceiveSocket 时会存在的锁。

        private bool _isMajorListenPeerInCurrentMachine; // 是否是这台机器的主接受端。

        private Socket _gameSocket; // 游戏用连接。

        private CancellationTokenSource _cancellationTokenSource; // 取消器。
        private Task _receiveThread, _boardcastThread, _debugThread; // 双线程。

        private byte[] _receiveBuffer, _boardcastBuffer; // 缓冲区。
        private MemoryStream _receiveMemoryStream; // 读取流。
        private MemoryStream _boardcastMemoryStream; // 广播流
        private RuntimeTypeModel _typeModel;

        private MessagePack _boardcastMessagePack;
        private AddressPortInfo _localAddressofPortInfo;

        #endregion

        #region ctors

        public Peer() : this(5000)
        {

        }

        public Peer(int port)
        {
            _receiveSocketCreateMutex = new Mutex(true, ReceiveSocketCreateMutexName, out var isCreated);
            _isMajorListenPeerInCurrentMachine = isCreated;

            AddressPortInfoList = new AddressPortStateList();
            _cancellationTokenSource = new CancellationTokenSource();
            _port = port;

            _receiveBuffer = new byte[BufferSize];
            _boardcastBuffer = new byte[BufferSize];

            _receiveMemoryStream = new MemoryStream(_receiveBuffer);
            _boardcastMemoryStream = new MemoryStream(_boardcastBuffer);

            _typeModel = TypeModelHelper.CreateMessagePackTypeModel();

            _boardcastMessagePack = new MessagePack() { Type = MessageType.Boardcast };

            InitMemoryShared(AddressPortInfoList);
        }

        #endregion

        #region methods

        public void Start()
        {
            _boardcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            _receiveThread = new Task(ProcessReceiveLoop);
            _debugThread = new Task(DebugEntry);
            _boardcastThread = new Task(ProcessBoardcastLoop);

            _receiveThread.Start();
            _debugThread.Start();
            _boardcastThread.Start();
        }

        #region receive

        ///<summary>处理循环。</summary>
        private async void ProcessReceiveLoop()
        {
            if (IsMajorListenPeerInCurrentMachine() == false) return;

            _receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            _receiveSocket.ReceiveBufferSize = BufferSize;
            _receiveSocket.SendBufferSize = BufferSize;

            var ipAddress = IPAddress.Any;
            var port = _port;
            var ipEndPoint = new IPEndPoint(ipAddress, port);
            var endPoint = ipEndPoint as EndPoint;

            _receiveSocket.Bind(endPoint);

            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                await ProcessReceiveEntry(endPoint);
            }
        }

        private async Task ProcessReceiveEntry(EndPoint endPoint)
        {
            try
            {
                await ProcessReceiveCore(endPoint);
            }
            catch (TaskCanceledException cancelEx)
            {
                return;
            }
            catch (Exception ex)
            {
                _cancellationTokenSource.Cancel();
                //Console.WriteLine(ex.Message);
            }
        }

        private async Task ProcessReceiveCore(EndPoint endPoint)
        {
            var result = await _receiveSocket.ReceiveFromAsync(_receiveBuffer, endPoint, _cancellationTokenSource.Token);
            _receiveMemoryStream.Seek(0, SeekOrigin.Begin);
            var messagePack = _typeModel.DeserializeWithLengthPrefix(_receiveMemoryStream, null, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1) as MessagePack;

            var remoteEndPoint = result.RemoteEndPoint;
            if (remoteEndPoint is IPEndPoint ipEndPoint)
            {
                //Console.WriteLine($"ProcessReceiveCore ip {ipEndPoint.Address} port {ipEndPoint.Port}");

                if (messagePack.Type == MessageType.Boardcast)
                {
                    AddressPortInfoList.AddNow(new AddressPortInfo(ipEndPoint));
                }
            }
            else
            {
                //Console.WriteLine($"ProcessReceiveCore failed");
            }
        }

        #endregion

        #region boardcast

        private void ProcessBoardcastLoop()
        {
            _boardcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            _boardcastSocket.ReceiveBufferSize = BufferSize;
            _boardcastSocket.SendBufferSize = BufferSize;

            var ipAddress = IPAddress.Broadcast;
            var port = _port;
            var ipEndPoint = new IPEndPoint(ipAddress, port);
            var endPoint = ipEndPoint as EndPoint;

            _boardcastSocket.Connect(endPoint);

            _localAddressofPortInfo = new AddressPortInfo(_boardcastSocket.LocalEndPoint as IPEndPoint);

            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                ProcessBoardcastEntry();
            }
        }

        public MessagePack GetBoardcastMessagePack()
        {
            return _boardcastMessagePack;
        }

        private void ProcessBoardcastEntry()
        {
            try
            {
                var boardcastMessagePack = GetBoardcastMessagePack();
                var ms = _boardcastMemoryStream;
                ms.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(ms, boardcastMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                _boardcastSocket.Send(_boardcastBuffer);
                //Console.WriteLine("boardcast send");
                Thread.Sleep(1000);
            }
            catch (OperationCanceledException cancelEx)
            {
                return;
            }
            catch (Exception ex)
            {
                _cancellationTokenSource.Cancel();
                Console.WriteLine(ex.Message);
            }
        }

        private void ProcessBoardcastCore()
        {

        }

        #endregion

        #region debug

        private void DebugEntry()
        {
            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                var infos = AddressPortInfoList.GetIdleAddressPortBoardcastInfos();
                var sb = new StringBuilder();

                sb.AppendLine($"local: {_localAddressofPortInfo}");
                var counter = 0;
                foreach (var info in infos)
                {
                    counter++;
                    sb.AppendLine($"{counter:d5}: {info}");
                }

                sb.AppendLine("----------------------------");

                Console.WriteLine(sb);

                Thread.Sleep(5 * 1000);
            }
        }

        #endregion


        ///<summary>是否是当前机器的主要接收端</summary>
        private bool IsMajorListenPeerInCurrentMachine()
        {
            return _isMajorListenPeerInCurrentMachine;
        }

        public void Dispose()
        {
            _cancellationTokenSource?.Cancel();

            DisposeMemoryShared();

            _receiveSocket?.Shutdown(SocketShutdown.Both);
            _boardcastSocket?.Shutdown(SocketShutdown.Both);
            _receiveSocket?.Close();
            _boardcastSocket?.Close();
            //_receiveSocket.Disconnect(false);
            //_boardcastSocket.Disconnect(false);
            _receiveSocket?.Dispose();
            _boardcastSocket?.Dispose();

            _cancellationTokenSource?.Dispose();

            _receiveMemoryStream?.Dispose();
            _boardcastMemoryStream?.Dispose();

            Console.WriteLine("peer finished");
        }

        #endregion

    }
}
